package com.lin.filegraph.utils.componentRule;

import com.lin.filegraph.utils.threshold.Threshold;
import com.lin.filegraph.utils.compdg.ComponentGraph;
import com.lin.filegraph.utils.compdg.ComponentNode;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

/**
 * @description:
 * @author: linhuaixu
 * @time: 2023/5/18 16:11
 */

public class LoopFinder {

    ComponentGraph graph;
    int fileNum = 0;

    List<List<ComponentNode>> loopList = new ArrayList<>();
    Set<ComponentNode> visitedNodes = new LinkedHashSet<>();
    boolean hasCycle = false;

    public LoopFinder(ComponentGraph cdg) {
        // TODO Auto-generated constructor stub
        this.graph = cdg;
        for(ComponentNode cNode : cdg.getAllComponents()) {
            fileNum += cNode.getAllFiles().size();
        }
    }

    public void execute() {
        for (int i = graph.getAllComponents().size() - 1; i >= 0; i--) {
            ComponentNode node = graph.getAllComponents().get(i);
            if (!visitedNodes.contains(node)) {
                List<ComponentNode> trace = new ArrayList<>();
                hasCycle = false;
                findLoop(node, trace);
                if (hasCycle) {
                    Set<ComponentNode> changedNodes = new LinkedHashSet<>();
                    for (List<ComponentNode> oneLoop : loopList) {
                        boolean isOverlap = false;
                        boolean hasRegion = false;
                        int regionCount = 0;
                        ComponentNode firstComp = oneLoop.get(0);
                        for (ComponentNode comp : oneLoop) {
                            if (changedNodes.contains(comp) || !graph.getAllComponents().contains(comp)) {
                                isOverlap = true;
                            }
                            if (comp.isRegion()) {
                                hasRegion = true;
                                regionCount++;
                            }
                        }
                        changedNodes.addAll(oneLoop);
                        if (!isOverlap && hasRegion && regionCount == 1) {
                            i -= graph.mergeNodes(oneLoop);
                        }
                    }
                }
            }
        }
    }

    private void findLoop(ComponentNode node, List<ComponentNode> trace) {

        if (trace.indexOf(node) != -1) {
            List<ComponentNode> oneLoop = new ArrayList<>(trace.subList(trace.indexOf(node), trace.size()));
            oneLoop.add(node);
            boolean hasRegion = false;
            int regionCount = 0;
            for (ComponentNode cNode : oneLoop) {
                if (cNode.isRegion()) {
                    hasRegion = true;
                    regionCount++;
                    break;
                }
            }

            if (hasRegion && regionCount == 1) {
                int tempNum = 0;
                for(ComponentNode cNode : oneLoop) {
                    tempNum += cNode.getAllFiles().size();
                }
                if(tempNum < fileNum * Threshold.COMSCALE){
                    hasCycle = true;
                    loopList.add(oneLoop);

                }

            }
            return;
        }
        trace.add(node);

        for (ComponentNode nextNode : graph.getPostComponents(node)) {
            if (!visitedNodes.contains(nextNode) && !nextNode.equals(node)) {
                findLoop(nextNode, trace);
            }
        }
        trace.remove(trace.size() - 1);
        visitedNodes.add(node);
    }

    public void executeIgnoreRegion() {
        for (int i = graph.getAllComponents().size() - 1; i >= 0; i--) {
            ComponentNode node = graph.getAllComponents().get(i);
            if (!visitedNodes.contains(node)) {
                List<ComponentNode> trace = new ArrayList<>();
                hasCycle = false;
                findLoopIgnoreRegion(node, trace);
                if (hasCycle) {
                    Set<ComponentNode> changedNodes = new LinkedHashSet<>();
                    for (List<ComponentNode> oneLoop : loopList) {
                        boolean isOverlap = false;
                        ComponentNode firstComp = oneLoop.get(0);
                        for (ComponentNode comp : oneLoop) {
                            if (changedNodes.contains(comp) || !graph.getAllComponents().contains(comp)) {
                                isOverlap = true;
                            }
                        }
                        changedNodes.addAll(oneLoop);
                        if (!isOverlap) {
                            i -= graph.mergeNodes(oneLoop);
                        }
                    }
                }
            }
        }
    }

    private void findLoopIgnoreRegion(ComponentNode node, List<ComponentNode> trace) {

        if (trace.indexOf(node) != -1) {
            List<ComponentNode> oneLoop = new ArrayList<>(trace.subList(trace.indexOf(node), trace.size()));
            int tempNum = 0;
            for(ComponentNode cNode : oneLoop) {
                tempNum += cNode.getAllFiles().size();
            }
            if(tempNum < fileNum * Threshold.COMSCALE){
                oneLoop.add(node);
                hasCycle = true;
                loopList.add(oneLoop);

            }
            return;
        }
        trace.add(node);

        for (ComponentNode nextNode : graph.getPostComponents(node)) {
            if (!visitedNodes.contains(nextNode) && !nextNode.equals(node)) {
                findLoopIgnoreRegion(nextNode, trace);
            }
        }
        trace.remove(trace.size() - 1);
        visitedNodes.add(node);
    }

    public List<List<ComponentNode>> getLoopList() {
        return loopList;
    }

    public ComponentGraph getGraph() {
        return graph;
    }

}

